﻿using Google.Protobuf;
using System;
using System.Net.Sockets;
using System.Text;

namespace DotNetClient
{
    public enum ProtocolState
    {
        start = 1,          // Just open, need to send handshaking
        handshaking = 2,    // on handshaking process
        working = 3,		// can receive and send data 
        closed = 4,		    // on read body
    }

    public class Protocol
    {
        private ProtocolState _state;
        private MessageProtocol _messageProtocol;
        private Transporter _transporter;
        private HandshakeService _handshakeService;
        private HeartbeatService _heartbeatService;

        public SocketClient Client { get; }

        public Protocol(SocketClient client, Socket socket)
        {
            Client = client;
            _transporter = new Transporter(socket, processMessage);
            _transporter.onDisconnect = onDisconnect;
            _handshakeService = new HandshakeService(this);
            _state = ProtocolState.start;
        }

        internal void start(Action callback)
        {
            _transporter.start();
            _handshakeService.request(callback);
            _state = ProtocolState.handshaking;
        }

        //-------------------------------------------------------------------------------
        //                              send
        //-------------------------------------------------------------------------------
        /// <summary>
        /// Send notify, do not need id
        /// </summary>
        /// <param name="route"></param>
        /// <param name="msg"></param>
        internal void send(string route, IMessage msg)
        {
            send(route, 0, msg);
        }

        /// <summary>
        /// Send request, user request id 
        /// </summary>
        /// <param name="route"></param>
        /// <param name="id"></param>
        /// <param name="msg"></param>
        internal void send(string route, byte id, IMessage msg)
        {
            if (_state != ProtocolState.working) return;

            byte[] body = _messageProtocol.encode(route, id, msg);
            send(PackageType.PKG_MESSAGE, body);
        }

        /// <summary>
        /// Send message use the transporter
        /// </summary>
        /// <param name="type"></param>
        /// <param name="body"></param>
        internal void send(PackageType type, byte[] body)
        {
            if (_state == ProtocolState.closed) return;

            _transporter.send(PackageProtocol.encode(type, body));
        }

        /// <summary>
        /// Send system message
        /// This method only used to send system package
        /// </summary>
        /// <param name="type"></param>
        internal void send(PackageType type)
        {
            if (type == PackageType.PKG_MESSAGE) return;

            _transporter.send(PackageProtocol.encode(type));
        }

        /// <summary>
        /// Send system message, these message do not use messageProtocol
        /// This method only used to send system package
        /// </summary>
        /// <param name="type"></param>
        /// <param name="msg"></param>
        internal void send(PackageType type, string msg)
        {
            if (type == PackageType.PKG_MESSAGE) return;

            byte[] body = Encoding.UTF8.GetBytes(msg);
            _transporter.send(PackageProtocol.encode(type, body));
        }

        //-------------------------------------------------------------------------------
        //                              process
        //-------------------------------------------------------------------------------
        internal void processMessage(Package package)
        {
            // Ignore all the message except handshading at handshake stage            
            if (package.type == PackageType.PKG_HANDSHAKE && _state == ProtocolState.handshaking) // 握手回调
            {
                string handshakeStr = Encoding.UTF8.GetString(package.body);
                //UnityEngine.Debug.Log("握手 " + handshakeStr);
                processHandshakeData(handshakeStr);
                _state = ProtocolState.working;
            }
            else if (package.type == PackageType.PKG_HEARTBEAT && _state == ProtocolState.working) // 心跳回调
            {
                //UnityEngine.Debug.Log("心跳");
                _heartbeatService.resetTimeout();
            }
            else if (package.type == PackageType.PKG_MESSAGE && _state == ProtocolState.working)  // 自定义消息
            {
                _heartbeatService.resetTimeout();
                Client.ProcessMessage(_messageProtocol.decode(package.body));
            }
            else if (package.type == PackageType.PKG_KICK)
            {
                Client.Disconnect();
                close();
            }
        }

        private void processHandshakeData(string msg)
        {
            Message_Handshake handshakeMsg = UnityEngine.JsonUtility.FromJson<Message_Handshake>(msg);

            // Handshake error
            if (handshakeMsg == null)
            {
                throw new Exception("Handshake error! Please check your handshake config.");
            }

            //Set compress data
            _messageProtocol = new MessageProtocol(handshakeMsg.route);

            // Init heartbeat service
            _heartbeatService = new HeartbeatService(handshakeMsg.heartbeat, this);
            if (handshakeMsg.heartbeat > 0)
            {
                _heartbeatService.start();
            }

            // send ack and change protocol state
            _handshakeService.ack();
            _state = ProtocolState.working;

            // Invoke handshake callback
            _handshakeService.invokeCallback(handshakeMsg);
        }

        //-------------------------------------------------------------------------------
        //                              disconnect
        //-------------------------------------------------------------------------------
        private void onDisconnect()
        {
            Client.Disconnect();
        }

        internal void close()
        {
            _transporter?.close();
            _heartbeatService?.stop();
            _state = ProtocolState.closed;
        }
    }
}
